/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       @karlmacklin <tacklemcclean@gmail.com>
 * @copyright    2016 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * DeviceButtons belong to both `Phaser.Pointer` and `Phaser.SinglePad` (Gamepad) instances.
 *
 * For Pointers they represent the various buttons that can exist on mice and pens, such as the left button, right button,
 * middle button and advanced buttons like back and forward.
 *
 * Access them via `Pointer.leftbutton`, `Pointer.rightButton` and so on.
 *
 * On Gamepads they represent all buttons on the pad: from shoulder buttons to action buttons.
 *
 * At the time of writing this there are device limitations you should be aware of:
 *
 * - On Windows, if you install a mouse driver, and its utility software allows you to customize button actions
 *   (e.g., IntelliPoint and SetPoint), the middle (wheel) button, the 4th button, and the 5th button might not be set,
 *   even when they are pressed.
 * - On Linux (GTK), the 4th button and the 5th button are not supported.
 * - On Mac OS X 10.5 there is no platform API for implementing any advanced buttons.
 *
 * @class Phaser.DeviceButton
 * @constructor
 * @param {Phaser.Pointer|Phaser.SinglePad} parent - A reference to the parent of this button. Either a Pointer or a Gamepad.
 * @param {number} buttonCode - The button code this DeviceButton is responsible for.
 */
Phaser.DeviceButton = function (parent, buttonCode)
{
    /**
     * @property {Phaser.Pointer|Phaser.SinglePad} parent - A reference to the Pointer or Gamepad that owns this button.
     */
    this.parent = parent;

    /**
     * @property {Phaser.Game} game - A reference to the currently running game.
     */
    this.game = parent.game;

    /**
     * @property {object} event - The DOM event that caused the change in button state.
     * @default
     */
    this.event = null;

    /**
     * @property {boolean} isDown - The "down" state of the button.
     * @default
     */
    this.isDown = false;

    /**
     * @property {boolean} isUp - The "up" state of the button.
     * @default
     */
    this.isUp = true;

    /**
     * @property {number} timeDown - The timestamp when the button was last pressed down.
     * @default
     */
    this.timeDown = 0;

    /**
     * @property {number} timeUp - The timestamp when the button was last released.
     * @default
     */
    this.timeUp = 0;

    /**
     * Gamepad only.
     * If a button is held down this holds down the number of times the button has 'repeated'.
     * @property {number} repeats
     * @default
     */
    this.repeats = 0;

    /**
     * True if the alt key was held down when this button was last pressed or released.
     * Not supported on Gamepads.
     * @property {boolean} altKey
     * @default
     */
    this.altKey = false;

    /**
     * True if the shift key was held down when this button was last pressed or released.
     * Not supported on Gamepads.
     * @property {boolean} shiftKey
     * @default
     */
    this.shiftKey = false;

    /**
     * True if the control key was held down when this button was last pressed or released.
     * Not supported on Gamepads.
     * @property {boolean} ctrlKey
     * @default
     */
    this.ctrlKey = false;

    /**
     * @property {number} value - Button value. Mainly useful for checking analog buttons (like shoulder triggers) on Gamepads.
     * @default
     */
    this.value = 0;

    /**
     * @property {number} buttonCode - The buttoncode of this button if a Gamepad, or the DOM button event value if a Pointer.
     */
    this.buttonCode = buttonCode;

    /**
     * This Signal is dispatched every time this DeviceButton is pressed down.
     * It is only dispatched once (until the button is released again).
     * When dispatched it sends 2 arguments: A reference to this DeviceButton and the value of the button.
     * @property {Phaser.Signal} onDown
     */
    this.onDown = new Phaser.Signal();

    /**
     * This Signal is dispatched every time this DeviceButton is released from a down state.
     * It is only dispatched once (until the button is pressed again).
     * When dispatched it sends 2 arguments: A reference to this DeviceButton and the value of the button.
     * @property {Phaser.Signal} onUp
     */
    this.onUp = new Phaser.Signal();

    /**
     * Gamepad only.
     * This Signal is dispatched every time this DeviceButton changes floating value (between, but not exactly, 0 and 1).
     * When dispatched it sends 2 arguments: A reference to this DeviceButton and the value of the button.
     * @property {Phaser.Signal} onFloat
     */
    this.onFloat = new Phaser.Signal();
};

Phaser.DeviceButton.prototype = {

    /**
     * Called automatically by Phaser.Pointer and Phaser.SinglePad.
     * Handles the button down state.
     *
     * @method Phaser.DeviceButton#start
     * @protected
     * @param {object} [event] - The DOM event that triggered the button change.
     * @param {number} [value] - The button value. Only get for Gamepads.
     */
    start: function (event, value)
    {
        if (this.isDown)
        {
            return;
        }

        this.isDown = true;
        this.isUp = false;
        this.timeDown = this.game.time.time;
        this.repeats = 0;

        this.event = event;
        this.value = value;

        if (event)
        {
            this.altKey = event.altKey;
            this.shiftKey = event.shiftKey;
            this.ctrlKey = event.ctrlKey;
        }

        this.onDown.dispatch(this, value);
    },

    /**
     * Called automatically by Phaser.Pointer and Phaser.SinglePad.
     * Handles the button up state.
     *
     * @method Phaser.DeviceButton#stop
     * @protected
     * @param {object} [event] - The DOM event that triggered the button change.
     * @param {number} [value] - The button value. Only get for Gamepads.
     */
    stop: function (event, value)
    {
        if (this.isUp)
        {
            return;
        }

        this.isDown = false;
        this.isUp = true;
        this.timeUp = this.game.time.time;

        this.event = event;
        this.value = value;

        if (event)
        {
            this.altKey = event.altKey;
            this.shiftKey = event.shiftKey;
            this.ctrlKey = event.ctrlKey;
        }

        this.onUp.dispatch(this, value);
    },

    /**
     * Called automatically by Phaser.Pointer.
     * Starts or stops button based on condition.
     *
     * @method Phaser.DeviceButton#startStop
     * @protected
     * @param {boolean} [condition] - The condition that decides between start or stop.
     * @param {object} [event] - The DOM event that triggered the button change.
     * @param {number} [value] - The button value. Only get for Gamepads.
     */
    startStop: function (condition, event, value)
    {
        if (condition)
        {
            this.start(event, value);
        }
        else
        {
            this.stop(event, value);
        }
    },

    /**
     * Called automatically by Phaser.SinglePad.
     *
     * @method Phaser.DeviceButton#padFloat
     * @protected
     * @param {number} value - Button value
     */
    padFloat: function (value)
    {
        this.isDown = false;
        this.isUp = false;

        this.value = value;

        this.onFloat.dispatch(this, value);
    },

    /**
     * Returns the "just pressed" state of this button.
     * Just pressed is considered true if the button was pressed down within the duration given (default 250ms).
     *
     * @method Phaser.DeviceButton#justPressed
     * @param {number} [duration=250] - The duration in ms below which the button is considered as being just pressed.
     * @return {boolean} True if the button is just pressed otherwise false.
     */
    justPressed: function (duration)
    {
        duration = duration || 250;

        return (this.isDown && (this.timeDown + duration) > this.game.time.time);
    },

    /**
     * Returns the "just released" state of this button.
     * Just released is considered as being true if the button was released within the duration given (default 250ms).
     *
     * @method Phaser.DeviceButton#justReleased
     * @param {number} [duration=250] - The duration in ms below which the button is considered as being just released.
     * @return {boolean} True if the button is just released otherwise false.
     */
    justReleased: function (duration)
    {
        duration = duration || 250;

        return (this.isUp && (this.timeUp + duration) > this.game.time.time);
    },

    /**
     * Resets this DeviceButton, changing it to an isUp state and resetting the duration and repeats counters.
     *
     * @method Phaser.DeviceButton#reset
     */
    reset: function ()
    {
        this.isDown = false;
        this.isUp = true;

        this.timeDown = this.game.time.time;
        this.repeats = 0;

        this.altKey = false;
        this.shiftKey = false;
        this.ctrlKey = false;
    },

    /**
     * Destroys this DeviceButton, this disposes of the onDown, onUp and onFloat signals
     * and clears the parent and game references.
     *
     * @method Phaser.DeviceButton#destroy
     */
    destroy: function ()
    {
        this.onDown.dispose();
        this.onUp.dispose();
        this.onFloat.dispose();

        this.parent = null;
        this.game = null;
    }

};

Phaser.DeviceButton.prototype.constructor = Phaser.DeviceButton;

/**
 * How long the button has been held down for in milliseconds.
 * If not currently down it returns -1.
 *
 * @name Phaser.DeviceButton#duration
 * @property {number} duration
 * @readonly
 */
Object.defineProperty(Phaser.DeviceButton.prototype, 'duration', {

    get: function ()
    {
        if (this.isUp)
        {
            return -1;
        }

        return this.game.time.time - this.timeDown;
    }

});
